﻿using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;

namespace mebc4.tablebased.synchronous
{
    [TestFixture]
    public class test_Runtime
    {
        // Processing of messages is depth first: split, tolower, map, build, map, build, map, build...
        // Sending functional unit is "suspended" until receiving FU has finished processing the message.
        [Test]
        public void Run()
        {
            var runtime = new MebcRuntime();

            // register functional units
            // * wire them to input pins
            // * redirect their output to output pins
            runtime.Reg<string>("split.process", x => runtime.Post("split.result", SplitMap.Split(x)));
            
            runtime.Reg<IEnumerable<string>>("tolower.process", x => SplitMap.ToLower(x, o => runtime.Post("tolower.result", o)));
            
            runtime.Reg<string>("map.process", x => SplitMap.Map(x, o => runtime.Post("map.result", o)));

            var build = new Build();
            build.Result += x => runtime.Post("build.result", x);
            runtime.Reg<KeyValuePair<string, string>>("build.process", build.Process);

            runtime.Reg<Dictionary<string, string>>("out", x => Console.WriteLine("result: {0}", x.Count));

            // map output pins to input pins
            runtime.Map("in", "split.process");
            runtime.Map("split.result", "tolower.process");
            runtime.Map("tolower.result", "map.process");
            runtime.Map("map.result", "build.process");
            runtime.Map("build.result", "out");

            runtime.Post("in", "A=1;B=2");
        }

        
        [Test]
        public void Register_methods_individually()
        {
            var runtime = new MebcRuntime();

            runtime.RegFunc<string, IEnumerable<string>>("split", SplitMap.Split);
            runtime.RegProc<IEnumerable<string>, string>("tolower", SplitMap.ToLower);
            runtime.RegProc<string, KeyValuePair<string,string>>("map", SplitMap.Map);

            var build = new Build();
            build.Result += x => runtime.Post("build", x);
            runtime.Reg<KeyValuePair<string, string>>("build", build.Process);

            runtime.RegProc<Dictionary<string, string>>("out", x => Console.WriteLine("result: {0}", x.Count));

            runtime.Map("in", "split");
            runtime.Map("split", "tolower");
            runtime.Map("tolower", "map");
            runtime.Map("map", "build");
            runtime.Map("build", "out");

            runtime.Post("in", "A=1;B=2");
        }


        [Test]
        public void Register_static_methods()
        {
            var runtime = new MebcRuntime();

            runtime.RegStaticMethods<SplitToLower>();
            runtime.RegProc<string[]>("out", x => Console.WriteLine(string.Join("/", x)));

            runtime.Map("in", "SplitToLower.Split");
            runtime.Map("SplitToLower.Split", "SplitToLower.ToLower");
            runtime.Map("SplitToLower.ToLower", "out");

            runtime.Post("in", "A;B;C");
        }

        public class SplitToLower
        {
            public static IEnumerable<string> Split(string input)
            {
                return input.Split(';');
            }

            public static void ToLower(IEnumerable<string> input, Action<string[]> output)
            {
                output(input.Select(s => s.ToLower()).ToArray());
            }
        }


        [Test]
        public void Register_instance_methods()
        {
            var runtime = new MebcRuntime();

            runtime.RegInstanceMethods(new SplitToUpper());
            runtime.RegProc<string[]>("out", x => Console.WriteLine(string.Join("/", x)));

            runtime.Map("in", "SplitToUpper.Split");
            runtime.Map("SplitToUpper.Split", "SplitToUpper.ToUpper");
            runtime.Map("SplitToUpper.ToUpper", "out");

            runtime.Post("in", "a;b;c");
        }

        public class SplitToUpper
        {
            public IEnumerable<string> Split(string input)
            {
                return input.Split(';');
            }

            public void ToUpper(IEnumerable<string> input, Action<string[]> output)
            {
                output(input.Select(s => s.ToUpper()).ToArray());
            }
        }


        [Test]
        public void Register_EBC()
        {
            var runtime = new MebcRuntime();

            runtime.RegEventBasedOp(new EbcSplit());
            runtime.RegEventBasedOp(new EbcToUpper());
            runtime.RegProc<string[]>("out", x => Console.WriteLine(string.Join("/", x)));

            runtime.Map("in", "EbcSplit.Process");
            runtime.Map("EbcSplit.Result", "EbcToUpper.Process");
            runtime.Map("EbcToUpper.Result", "out");

            runtime.Post("in", "a;b;c");
        }


        public class EbcSplit
        {
            public void Process(string input)
            {
                Result(input.Split(';'));
            }

            public event Action<IEnumerable<string>> Result;
        }

        public class EbcToUpper
        {
            public void Process(IEnumerable<string> input)
            {
                Result(input.Select(s => s.ToUpper()).ToArray());
            }

            public event Action<string[]> Result;
        }


        [Test]
        public void Register_multi_output_proc()
        {
            var runtime = new MebcRuntime();

            runtime.RegInstanceMethods(new JustOneAction());
            runtime.RegProc<string>("out0", x => Console.WriteLine("result: {0}", x));
            runtime.RegProc<int>("out1", x => Console.WriteLine("count: {0}", x));

            runtime.Map("in", "JustOneAction.ToLowerCount");
            runtime.Map("JustOneAction.ToLowerCount.Lowered", "out0");
            runtime.Map("JustOneAction.ToLowerCount.Counted", "out1");

            runtime.Post("in", "abc");
        }


        class JustOneAction
        {
            public void ToLowerCount(string input, Action<string> Lowered, Action<int> Counted)
            {
                Lowered(input.ToLower());
                Counted(input.Length);
            }
        }


        [Test]
        public void Register_methods_with_scalar_params()
        {
            var runtime = new MebcRuntime();

            runtime.RegInstanceMethods(new ScalarParams());
            runtime.RegProc<double>("out", x => Console.WriteLine("result: {0}", x));

            runtime.Map("in", "ScalarParams.Half");
            runtime.Map("ScalarParams.Half", "out");

            runtime.Post("in", 7);
        }


        class ScalarParams
        {
            public void Half(int d, Action<double> half)
            {
                half((double)d/2);
            }
        }
    }
}
